#!/usr/bin/python2.7
# -*- coding: cp1252 -*-
#
# Copyright 2012 Darkspace revelations ltd. All Rights Reserved.
# adapted from the Startrek and Spectrek BASIC versions of way back (1970' - 1980's)

"""

    Spectrek.py
    
    Main program for CoopyTrek
    on Google App Engine

"""

from datetime import datetime 
import time
import logging
import os
import random
#import re
import sys
#import traceback
import json
import pickle
import math
from google.appengine.api import channel
#from google.appengine.api import memcache
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext  import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app

from coopymission import *
from coopyentity import *

sessionmanager = None


class ActiveQuadrant():
    """ active quadrant holds the list of ship entities and the list of entities in
        an active quadrant - temporary instance for processing commands and ai """
    ships = None
    entities = None
    
    def __init__(self, game, qx=0, qy=0):
        """ load the entities on initialisation if a game is specified"""
        if game:
            self.load(game,qx,qy)

    def load(self, game, qx,qy):
        self.entities = game.quadrant_entities(qx,qy)
        self.ships = game.quadrant_get_entity_type(qx,qy,[ENTITY_SHIP])

    def discard(self):
        self.entities = None
        self.ships = None

    def get_closest(self, target, entities):
        """ return (entity, its distance to target) out of types entities closest to target """
        dist = 100
        closest_entity = None
        for e in self.entities:
            if (e.etype in entities) and (e<>target):
                sdist = math.hypot(target.x-e.x,target.y-e.y) #sdist = sqrt((target.x - e.x)**2 + (target.y - e.y)**2 )
                if (sdist<dist) :
                    dist = sdist
                    closest_entity = e
        return (closest_entity, dist)

    def get_point_on_vector_nearest_to_entity(self, o, r, (vx,vy), entities):
        loc = None
        dist = r
        for e in self.entities:
            if (e.etype in entities) and (e<>o):
                l = ((e.x-o.x)*vx+(e.y-o.y)*vy) / (vx**2+vy**2) # r dot ep (p=point on line)
                px = o.x+vx*l
                py = o.y+vx*l
                d = math.sqrt((px-e.x)**2+(py-e.y)**2)
                if (d<=dist):
                    dist = d
                    loc = (px,py)
        return loc

    def in_range(self, (x,y), r, entities):
        """ return the entities in this quadrant of type entities in range r withing location (x,y) """
        e_inrange = []
        for e in self.entities:
            if (e.etype in entities):
                d = math.hypot(x-e.x,y-e.y)
                if (d<=r):
                    ne = (e,d)
                    e_inrange.append(ne)
        return e_inrange

    def has_entity (self, entities) :
        for e in self.entities:
            if (e.etype in entities):
                return True
        return False

    
class Game():
    """ a game can host multiple players, has a galaxy made of quadrants
        game object is stored and fetched to process commands

    """
    # class variables used in game    

    quadrant_mx = 20
    quadrant_my = 20
    galaxy_mx = 8
    galaxy_my = 8    
    game_key = None
    entity_key = 0
    ships = {}
    aq = None
    
    def __init__(self, key, mission=0):
        """ Initializes the overall game parameters """
        random.seed()
        # game key ID and datastore models are kept in the instance

        self.game_key = key
        self.lock = None
        self.state = None
   
        self.name = missions[mission]["title"]
        
        self.entities = set([])
        self.global_entities = set([])
        self.resend_entities = True
        self.ships = {}
        self.rounds = {}
        self.stardate_start = 4800 + random.randint(0,199) 
        self.stardate = float(self.stardate_start)
        self.mission = Mission(self, mission)
        self.broadcasts = []

    def assign_entity_key(self):
        self.entity_key += 1
        return self.entity_key

    def quadrant_entities (self, qx,qy):
        es = []
        for e in self.entities:
            if e.qx==qx and e.qy==qy:
                es.append(e)
        return es

    def quadrant_collision(self, qx,qy, x, y):
        es = []
        for e in self.quadrant_entities(qx,qy):
            if e.collision(qx, qy, x,y):
                es.append(e)
        return es
 
    # replaced with mission.add_entity
    def add_entity(self, entity, quadrant=None, sector=None, range=0):
        b = False
        t = 0
        if quadrant:
            (entity.qx,entity.qy) = quadrant
        if sector:
            (x,y) = sector
            while not(b) and (t<20):
                b = True
                entity.x = x+random.randint(-range-t/10,range+t/10)
                entity.y = y+random.randint(-range-t/10,range+t/10)
                b = bool(self.quadrant_collision(entity.qx,entity.qy, entity.x,entity.y))
                t += 1
        self.entities.add(entity)
        if entity.isglobal:
            self.global_entities.add(entity)
                
    def quadrant_has_entity_type (self, qx, qy, entity_types):
        b = False
        for e in self.quadrant_entities(qx,qy):
            b = (e.etype in entity_types)
            if b: break
        return b

    def quadrant_get_entity_type (self, qx,qy, entity_types):
        """ returns the list of entities in the quadrant that are of any of entity_types """
        r = []
        for e in self.quadrant_entities(qx,qy):
            if (e.etype in entity_types):
                r.append(e)
        return r        
        
    def add_ship(self, user, rank, commander, tech={"photon":10,"energymax":15000,"hullmax":100}):
        """ Initializes the user's ship parameters """
        ship = Ship(game=self, user=user, commander=commander, rank=rank, **tech)
        ship.end = 0
        spawn = self.mission.playerSpawn(len(self.ships))
        self.add_entity(ship, spawn["quadrant"], spawn["sector"],0)
        self.ships.update ({user.user_id() : ship})
        ship.force_resend_entities = True # ensure the entity information is sent
        logging.debug("Added ship for "+str(user.user_id())+" "+str(len(self.ships)))
        self.broadcasts.append("Ship of " +  str(ship.rank) + " " + str(ship.commander) + " entered the mission.")

    def remove_ship(self, ship):
        """ takes the user's ship out of the game """
        self.broadcast("Ship of " +  str(ship.rank) + " " + str(ship.commander) + " left the mission.")
        self.entities.discard(ship)
        logging.debug("Removed ship: "+str(ship.user_id))
        self.ships.pop(ship.user_id)

    def new_location(self, (qx,qy), (x,y),(dqx,dqy), (dx, dy)):
        t = 0
        b = True
        newq = False
        while b and (t<10):
            b = False
            if (dx+x) < 0:
                dx += self.quadrant_mx
                dqx -= 1
                b = True
            if (dx+x) >= self.quadrant_mx:
                dx -= self.quadrant_mx
                dqx += 1
                b = True
            if (y+dy) < 0:
                dy += self.quadrant_my
                dqy -= 1
                b = True
            if (dy+y) >= self.quadrant_my:
                dy -= self.quadrant_my
                dqy += 1
                b = True
            t += 1
        if dqx<>0 or dqy<>0:
            newq = True
        ingalaxy = (qx+dqx>=0) and (qx+dqx<self.galaxy_mx) and (qy+dqy>=0) and (qy+dqy<=self.galaxy_my)
        return (ingalaxy, newq, (dqx, dqy), (dx,dy), (0,0))

    def broadcast(self, broadcast_message):
        """ send the broadcasts via the user channels to clients """
        self.broadcasts.append(broadcast_message)
        if True or len(self.broadcasts)>4 or ((time.time()-self.broadcasttime)>5):  # for now always
            gameUpdate = {'broadcasts' : self.broadcasts}
            logging.debug(gameUpdate)
            s = json.dumps(gameUpdate)
            for ship in self.ships.values():
                channel.send_message(ship.user_id + self.game_key, s)
            self.broadcasts = []
            self.broadcasttime = time.time()
        
    def advance_round(self, ship):
        """ advances the round in the ship's quadrant
            if ship forces advance with go == 1  or all ships in the ship's quadrant have commands
            lock command setting
            execute the commands
            execute the ai of the other vessels
            broadcast animation events to clients
        """

        def all_have_commands(s):
            c = True
            for ship in s:
                c = c and ship.command
            return c
        
        animations = []
        logging.debug ("Advancing for "+ str(ship.user_id) +  " , go=" + str(ship.command.go))
        
        self.aq = ActiveQuadrant(self, ship.qx, ship.qy)
        if self.aq.ships:
            logging.debug(len(self.aq.ships))
        else:
            logging.error("No ships found in this quadrant!!")
        if (ship.command.go == 1) or all_have_commands(self.aq.ships):
            for e in self.aq.entities:
                e.pre_round_update()           
            for s in self.aq.ships:
                logging.debug ("Evaluating for ship : " + s.commander)
                s.command_locked = True
                if s.alive and s.command:
                    s.command.execute() # Note: may need to be executed ordered by speed of command execution!
            for e in self.aq.entities:
                if e.has_ai:
                    e.command = None
                    e.ai(self)
            for e in self.global_entities:
                e.end_round_update_globals()
            self.mission.evaluate()
            for e in self.aq.entities:
                e.end_round_update()
                for a in e.animation:
                    animations.append(a)
                    e.animation = []
            for s in self.aq.ships:
                logging.debug ("Next round for ship : " + s.commander+", "+s.user.user_id())
                channel.send_message(s.user.user_id() + self.game_key, json.dumps({'animations' : animations}))
                self.goto_next_round_ship(s)
            self.resend_entities = False
            self.aq.discard()
                
    def send_game_update(self, ship):
        statusmessage = {"stardate" : self.stardate,
        				 "mission" : self.name,
        				 "score" : ship.score,
        				 "quadrant" : str(ship.qx) + ", " + str(ship.qy),
        				 "sector" : str(ship.x) + ", " + str(ship.y),
        				 "damage" : ship.damage_p(), 
        				 "shield":  ship.shield_p(),
        				 "energy" : ship.energy,
        				 "photons" : ship.photon,
        				 "photonsmax" : ship.photonmax,
        				}
        gameUpdate = {
            'pstatus': statusmessage,
            'broadcasts' : self.broadcasts,
            'ship' : ship.jsonable(),
            'objectives' : self.mission.objectives}
        if ship.inspace:
            if (self.resend_entities or ship.force_resend_entities):
                logging.debug ("Sending entity data")
                ship.force_resend_entities = False
                entitiesjs = []
                for e in self.quadrant_entities(ship.qx, ship.qy):
                    entitiesjs.append(e.jsonable())
                    logging.debug ("Entity added: ")
                    logging.debug (entitiesjs[-1])
                gameUpdate.update({'entities': entitiesjs})
            else:
                logging.debug ("Just sending label data")
                labels = []
                for e in self.aq.entities:
                    labels.append({"key": e.key, "elabel" : e.elabel(), "damage" : e.damage_p()})
                gameUpdate.update({'labellist': labels})
            if ship.command_updates:
                gameUpdate.update({"commands": ship.command_updates})
        if ship.mezzo:
            gameUpdate.update({"mezzo" : ship.mezzo})
            ship.mezzo = None
        if ship.end>0:
            gameUpdate.update({'the_end' : {"reason" : ship.end, "msg" : ship.endmsg}})
        s = json.dumps(gameUpdate)
        logging.debug(s)
        channel.send_message(ship.user_id + self.game_key, s)

    def goto_next_round_ship(self, ship):
        """ evaluate the ship's status and set up client for next round """
        global sessionmanager
        # insert the various health checks
        if ship.end > 0:
            self.remove_ship(ship)
            self.send_game_update(ship)
            sessionmanager.leave_game(self,ship.user_id)
        else:
            ship.command = None
            ship.command_locked = False
            if ship.entered_new_quadrant():
                logging.debug("Ship entered new quadrant, getting correct entities")
                aq = ActiveQuadrant(self, ship.qx, ship.qy)
            else:
                aq = self.aq
            ship.update_commands(aq)
            self.send_game_update(ship)
        
    def end_mission(self, success=True):
        """ end mission, divvy out scores from ships to players, show epilogue """
        if success:
            for s in self.ships.values():
                sessionmanager.set_spoils(s, self.mission)
                s.end = 1
                s.endmsg = "Mission completed."
                self.send_game_update(s)
                sessionmanager.leave_game(self, s.user)  
        
    def end(self):
        """ end this game """


class Command():
    game = None
    statusmessage = None

    def __init__(self, game, user_id, cmnd, go, args):
        self.game = game
        self.user_id = user_id
        self.ship = game.ships.get(user_id)
        self.cmnd = cmnd
        self.go = go
        self.args = args

    def _cimpulse_(self, vector=(0,0), force=0, **args):
        (x,y) = vector
        k = 10
        if self.ship.damage_p() > 50:
            send_alert("Impulse drive is damaged!", self.ship.user_id, self.game.game_key)
            k = k - int(self.ship.damage_p()/20)
        w = max(0,min(force,k))
        logging.debug (x)
        logging.debug (y)
        logging.debug (w)
        self.ship.command_duration = int(w*150)
        self.ship.energy -= 20*w
        self.ship.travel(self.game, (0,0),(int(x*w), int(y*w)))
        self.ship.command_locked = False

    def _chyper_(self, vector=(0,0), force=0, **args):
        logging.debug (self)
        logging.debug (vector)
        logging.debug (str(force))
        logging.debug (self.game)
        logging.debug (self.ship)
        (qx,qy) = vector
        k = 5
        if self.ship.damage_p() > 30:
            send_alert("Hyperdrive is damaged!", self.ship.user_id, self.game.game_key)
            k = k - int(self.ship.damage_p()/25)
        w = max(0,min(force,k))
        self.ship.command_duration = int(w*300)
        self.ship.energy -= 100*w
        self.ship.travel(self.game, (int(qx*w), int(qy*w)), (0,0))
        self.ship.command_locked = False

    def _cshield_(self, force=0, **args):
        energy = max(0,force)
        self.ship.shield += energy
        self.ship.energy -= energy
        self.ship.shield = min(self.ship.shield, self.ship.shieldmax)
        send_alert("Charged shields with "+str(energy)+".", self.ship.user_id, self.game.game_key)
        self.ship.command_locked = False
        
    def _cphaser_(self, vector=(0,0), force=0, **args):
        energy = max(0,force)
        (closest_enemy,dist) = self.game.aq.get_closest(self.ship, [ENTITY_KLINGON, ENTITY_BORG])
        self.ship.energy -= energy
        if (closest_enemy and (dist<self.ship.range_phaser)):
            closest_enemy.take_damage(energy, self.ship) # consider adding a falloff based on dist    
        
    def _cphoton_(self, vector=(0,0), **args):
        if self.ship.photon>0:
            self.ship.photon -=1
            (exp_x, exp_y) = self.game.aq.get_point_on_vector_nearest_to_entity(self.ship, self.ship.range_photon, vector, [ENTITY_KLINGON, ENTITY_BORG])
            if exp_x:
                for (entity,dist) in self.game.aq.in_range((exp_x,exp_y),4.0, [ENTITY_KLINGON, ENTITY_BORG]):
                    damage = int(25.0*(4.0-dist))
                    entity.take_damage(damage, self.ship)
            self.ship.animation.append({ "key" : self.ship.key,
                                    "animationtype" : "photon",
                                    "x" : self.ship.x,
                                    "y" : self.ship.y,
                                    "px" : exp_x,
                                    "py" : exp_y,
                                    "angle" : int(math.atan2(float(v[1]),float(v[0]))*180/math.pi),
                                    "delay" : self.ship.command_startfrom,
                                    "duration" : self.ship.command_duration})
        else:
            send_alert("Ship has no photons!", self.ship.user_id, self.game.game_key)
                       
    def _cdock_(self, **args):
        """ takes ship out of round progression and shows Starbase mezzo """
        self.ship.inspace=False
        cost_repair = 1.0 * (self.ship.hullmax-self.ship.hull)
        cost_recharge = 0.5 * (self.ship.energymax-self.ship.energy) 
        starbases = self.game.quadrant_get_entity_type (self.ship.qx,self.ship.qy, [ENTITY_STARBASE])
        if starbases:
            starbase = starbases[0]
            #starbaseoptions = starbase.options()
        else:
            logging.ERROR ("Ship docks in quadrant with NO starbase")
            starbase.name = "ERROR"
            starbase.type = "ERROR"
            starbase.director = "ERROR"
            starbaseoptions = {}

        # temporary options list
        starbaseoptions = { "repair" : {
                                "available" : True,
                                "cost" : cost_repair },
                            "charge" : {
                                "available" : True,
                                "fullchargecost" : cost_recharge },
                            "fitting" : {
                                "available" : True,
                                "photons" : True,
                                "shields" : True,
                                "hull" : True,
                                "cost" : 100 }
                            }
        template_values = { "starbase" : {
                                "type" : "Federation Class N Starbase", # starbase.type,
                                "name" : "Deepspace 5", # starbase.name,
                                "director" : "Boerstoel" },# starbase.director },
                            "commander" : {
                                "name" : self.ship.commander,
                                "rank" : self.ship.rank },
                            "option" : starbaseoptions,
                            "commands" : [
                                { "code"    : "FRP",
                                  "console" : "Full Repair",
                                  "cost" : cost_repair },
                                { "code"    : "MCR",
                                  "console" : "Max Charge",
                                  "cost" : cost_recharge },
                                { "code"    : "FIT",
                                  "console" : "Fitting Services",
                                  "cost" : 100 },
                                { "code"    : "LNC",
                                  "console" : "Launch",
                                  "cost" : 0 }] }    
        self.ship.mezzo = { "name" : "starbase docking",
                            "template" : "./mezzo/dock_starbase.html",
                            "show" : True,
                            "html" : template.render ("./mezzo/dock_starbase.html",template_values) }

    def _claunch_(self, vector=(0,0), **args):
        """ takes ship into space and round progression and discards the active mezzo """
        send_alert("Launching ship into space!", self.ship.user_id, self.game.game_key)
        self.ship.inspace=True
        self.ship.force_resend_entities = True
        self.ship.mezzo = { "discard" : True }               

    def _cfullrepair_(self, cost=0, **args):
        """ while docked: repair your ship completely """
        # elapsed time?
        # deduct credits =TBI
        self.ship.hull=self.ship.hullmax
        send_alert("Ship repaired fully", self.ship.user_id, self.game.game_key)
        send_alert("Cr. -"+str(cost), self.ship.user_id, self.game.game_key)
        
    def _cmaxcharge_(self, cost=0, **args):
        """ while docked: recharge your ship completely """
        # elapsed time?
        # deduct credits =TBI
        self.ship.energy=self.ship.energymax
        send_alert("Ship charged fully", self.ship.user_id, self.game.game_key)
        send_alert("Cr. -"+str(cost), self.ship.user_id, self.game.game_key)

    def _ccharge_(self, vector=(0,0), **args):
        """ near sun: recharge your ship slightly (2%)"""
        # elapsed time?
        # deduct credits =TBI
        if self.ship.energy<(self.ship.energymax*0.75):
            self.ship.energy+=(self.ship.energymax/50) # consider tech to collect more energy
            send_alert("Ship charged slightly", self.ship.user_id, self.game.game_key)
        else:
            send_alert("Ship energy level to high to collect energy", self.ship.user_id, self.game.game_key)
        
    def cpass (self, vector=(0,0), **args):
        pass

    def _illegalcommand_(self):
        pass

    def execute(self):
        commandfunctions = {"IMP" : self._cimpulse_,
                            "HYP" : self._chyper_,
                            "PRB" : self.cpass, #self.cprobe,
                            "PHO" : self._cphoton_,
                            "PHA" : self._cphaser_,
                            "SHI" : self._cshield_,
                            "SCN" : self.cpass, #self._cscan_,
                            "RPR" : self.cpass, #self.crepair,
                            "DCK" : self._cdock_,
                            "LNC" : self._claunch_,
                            "FRP" : self._cfullrepair_,
                            "MCR" : self._cmaxcharge_,
                            "CRG" : self._ccharge_,
                            "STOP": self.cpass,  #self.cstop,
                            "NONE" : self.cpass
                            }
        if self.cmnd in commandfunctions.keys():
            logging.debug(str(self.args))
            commandfunctions[self.cmnd](**self.args)
        else:
            self._illegalcommand_()


class CommandReceiver(webapp.RequestHandler):

    def post(self):
        """ adds the command returned from the client to their respective sip"""
        global sessionmanager
        game = None
        force = 0
        vector = (0,0)
        # identify user and game
        user = users.get_current_user()
        game_key = self.request.get('g')
        if user and game_key:
            game = sessionmanager.load_state(game_key)
            cmnd = self.request.get('cmnd')
            go = eval(self.request.get('go'))
            try:
                v = self.request.get('vector')
                vector = eval(v)
            except:
                vector = None
            try:
                f = self.request.get('force')
                force = eval(f)
            except:
                force = None
            try:
                c = self.request.get('cost')
                cost = eval(f)
            except:
                cost = None
            logging.debug("Entities in game: "+str(len(game.entities))+", Command: "+cmnd)
            ship = game.ships.get(user.user_id())
            if not(ship.command_locked):
                ship.command = Command(game, user.user_id(), cmnd, go, {'vector' : vector, 'force' : force, 'cost' : cost})
                if ship.inspace:
                    game.advance_round(ship)
                else:
                    ship.command.execute()
                    logging.debug(str(ship.qx) + ", " + str(ship.qy) + ", send entity data: " + str(ship.force_resend_entities))
                    game.send_game_update(ship)
                sessionmanager.save_state(game, game_key)


class EnterGame(webapp.RequestHandler):
    
    def get(self):
        """ Starts the mission game if it did not yet exist
            adds the player to the mission """
        
        """
            If no game exists: initialises the game
            If game exists: initialises the ship
            Renders the game page
        """
        global sessionmanager
        logging.debug ("Starting game page...")
        user = users.get_current_user()
        game_key = self.request.get('g')
        rendermode = self.request.get('renderMode')
        mission = int(self.request.get('mission',0))
        game = None
        if user:
            logging.debug ("User was logged in...")
            if not game_key:
                logging.debug ("Creating a game key...")
                game_key = user.user_id()
                logging.debug ("Initializing game...")
                game = sessionmanager.new_game(game_key, mission)
                logging.debug ("User joining game...")
            else:
                logging.debug ("Retrieving existing game...")
                game = sessionmanager.load_state(game_key)

            game_link = 'http://localhost:8080/?g=' + game_key

            if game:
                logging.debug ("Connecting to existing game...")
                token = channel.create_channel(user.user_id() + game_key)
                # cd = Command(game,user.user_id(), "NONE", 0, None)
                # logging.debug(cd.user_id)
                # logging.debug(cd.game.ships)
                template_values = {'token': token,
                                   'me': user.user_id(),
                                   'game_key': game_key,
                                   'game_link': game_link,
                                   'rendermode' : rendermode
                                  }
                path = os.path.join(os.path.dirname(__file__), 'game.html')
                self.response.out.write(template.render(path, template_values))
                sessionmanager.join_game(game, user)
                sessionmanager.save_state(game, game_key)
            else:
                self.response.out.write('No such game')
        else:
            self.redirect(users.create_login_url(self.request.uri))


class GameState(db.Model):
    content = db.TextProperty()
    date    = db.DateTimeProperty(auto_now_add=True)


class GameStateLock(db.Model):
    gamestate_key = db.StringProperty()
    locked = db.BooleanProperty()


class LobbyUser(db.Model):
    user = db.UserProperty()
    date = db.DateTimeProperty(auto_now_add=True)
    avatarx = db.IntegerProperty()
    avatary = db.IntegerProperty()
    rank = db.StringProperty()
    commander = db.StringProperty()
    ingame = db.StringProperty()
    ingame_name = db.StringProperty()
    score = db.IntegerProperty()
    credits = db.IntegerProperty()
    newmissions = db.StringProperty()
    missions = db.StringProperty()
    achievements = db.StringProperty()
    
    
    
class SessionManager():
    """ manages the connected users, the game instances and
        join and leave actions of users to games """

    def __init__(self):
        self.go = True

    def enter_lobby(self, user):
        """ user connects and is placed in the lobby """
        """ try to get the user's profile from the store,
        or otherwise create a profile """
        lobbyusers = LobbyUser.gql("WHERE user = :1", user)
        lobbyuser = lobbyusers.get()
        if not(lobbyuser):
            lobbyuser = LobbyUser()
            lobbyuser.user = user
            lobbyuser.put()
        return lobbyuser

    def new_game(self, key, mission):
        game = Game(key,mission)
        logging.debug ("new_game Key: "+ str(key) + " Game:" + str(game) + "  Mission: " + str(mission))
        self.save_state(game,key)
        return game
    
    def join_game(self, game, user):

        def fillout (p):
            if not(p.rank):
                p.rank = "unknown"
            if not(p.commander):
                p.commander = "unknown"
            
        """ joins the user to a game; checks whether user is not ingame """
        lobby_user = self.enter_lobby(user)
        profiles = UserProfile.gql("WHERE user = :1", user)
        profile = profiles.get()
        fillout(profile)
        lobby_user.ingame=game.game_key
        lobby_user.ingame_name = game.name
        lobby_user.date = datetime.now()
        (lobby_user.avatarx, lobby_user.avatary) = avatarspritepos(profile.avatar)
        lobby_user.rank = profile.rank
        lobby_user.commander = profile.commander
        lobby_user.put()

        logging.debug("Join game, adding ship for "+user.user_id())
        game.add_ship(user, profile.rank, profile.commander)

    def leave_game(self, game, user):
        """ user leaves the game he is ingame of
        if there are no ships in the game then stop the game"""
        game.remove_ship(game.ships.get(user.user_id()))
        if not(game.ships):
            #self.delete_state(game.game_key)
            game.end()
        lobbyusers = LobbyUser.gql("WHERE user = :1", user)
        lobbyuser = lobbyusers.get()
        if not(lobbyuser):
            lobbyuser = LobbyUser()
            lobbyuser.user = user
        else:
            lobbyuser.ingame = ""
            lobbyuser.date = datetime.now()
        
        lobbyuser.put()

    def list_lobby_users(self):

        q_lobby_users = LobbyUser.all()
        lobby_user_list = [{"user_id": lobby_user.user.user_id(),
                            "datetime" : lobby_user.date,
                            "avatarx": lobby_user.avatarx,
                            "avatary": lobby_user.avatary,
                            "rank" : lobby_user.rank,
                            "commander" : lobby_user.commander,
                            "ingame" : lobby_user.ingame,
                            "newmissions" : lobby_user.newmissions,
                            "missions" : lobby_user.missions,
                            "ingame_name" : lobby_user.ingame_name} for lobby_user in q_lobby_users]
        return lobby_user_list

    def acquire_state(self,key):

        def attempt_lock_state (key, lock):
            lock = db.get(lock_k)
            logging.debug ("Attempting: " + str(key) + ", " + str(lock))
            if not(lock.locked):
                lock.locked = True
                lock.put()
                return lock
            else:
                return None                    
        lock = None
        locked = True
        timeout_time = time.time()+1  ## change to longer later
        lock_k = db.Key.from_path('GameStateLock', key)
        logging.debug("Acquiring lock " + str(lock_k))
        while locked and time.time()<timeout_time:
            # after timeout we read the game state anyway
            #try:
            lock = db.run_in_transaction(attempt_lock_state, lock_k, lock)
            locked = (lock is None)
            time.sleep(0.2)
            #except:
            #    locked = False
        
        if not(locked):
            logging.debug ("Acquired lock: "+ str(lock_k)+ " : "+str(lock))
        else:
            logging.debug ("Acquire lock failed for key "+ key)
        return lock

    def release_state (self, game):
        """ release the game_state"""
        game.lock.locked=False
        game.lock.put()
    
    def load_state(self, key):
        """ load the gamestate from the store/memcache """

        game = None
        game_lock = self.acquire_state(key)
        if game_lock:
            gamestate_k = db.Key.from_path('GameState', key)
            logging.debug("Getting gamestate with key : "+str(gamestate_k))
            gamestate = db.get(gamestate_k)
            if gamestate:
                logging.debug("Loaded gamestate for " + key + " from db key " + str(gamestate_k))
                game = pickle.loads(str(gamestate.content))
                game.lock = game_lock
            else:
                logging.debug("Failed to load gamestate for "+key)
        return game

    def save_state(self, game, key):
        """ save the game state in the datasture on key=key, creating it if it did not exist """
        
        """ PRE: the game_state is locked for this user, or the game_state does not exist
            if no lock exists: create one and create a GameState instance"""

        gamestring = pickle.dumps(game)
        logging.debug("Saved gamestate")        
        if game.lock is None:
            game.lock = GameStateLock(key_name=key)
            gamestate = GameState(key_name=key)
        else:
            gamestate_k = db.Key.from_path('GameState', key)
            gamestate = db.get(gamestate_k)
        gamestate.content=db.Text(unicode(gamestring))
        gamestate.put()
        self.release_state(game)
    
    def set_spoils (self, ship, mission):
        logging.debug ("Setting spoils for "+str(ship)+", mission "+str(mission))
        lobbyusers = LobbyUser.gql("WHERE user = :1", ship.user)
        lobbyuser = lobbyusers.get()
        if lobbyuser:
            lobbyuser.score = ship.score
            lobbyuser.newmissions = mission.newmissions
            lobbyuser.achievements = "0000000000"
            lobbyuser.put()
        
    def get_spoils (self, user):
        lobbyusers = LobbyUser.gql("WHERE user = :1", user)
        lobbyuser = lobbyusers.get()
        newmissions = []
        i = 0
        imax = max(len(lobbyuser.newmissions),len(missions))-1
        if lobbyuser:
            for i in range(imax):
                if (lobbyuser.newmissions[i]=="1"):
                    newmissions.append(missions[i].get("title",""))
            spoils = {'score' : lobbyuser.score,
                    'credits' : lobbyuser.score,
                    'newmissions'  : json.dumps(newmissions),
                    'achievements' : lobbyuser.achievements }
        else:
            spoils = {'score' : 0,
                    'credits' : 0,
                    'newmissions'  : [],
                    'achievements' : [] }
        logging.debug ("Spoils: "+str(spoils))
        return spoils
    

        
    


"""-----------------------------
    Profile and Lobby functions

"""


class UserProfile(db.Model):
  """Models a user profile entry for the game."""
  user = db.UserProperty()
  rank = db.StringProperty()
  commander = db.StringProperty()
  avatar = db.IntegerProperty()
  date_created = db.DateTimeProperty(auto_now_add=True)
  last_login = db.DateTimeProperty()
  ships = db.StringProperty()  # [# standard ships, # ...]
  missions = db.StringProperty() # lists the status of the missions
  achievements = db.StringProperty()
  spacecredits = db.FloatProperty()
  

class CreateProfile(webapp.RequestHandler):
    """ Create a profile for the current user and store in the datastore """
    
    def get(self):
        user = users.get_current_user()
        if user:
            profile = UserProfile()
            profile.commander = "[enter a name]"
            profile.avatar = 0

            template_values = { 'commander': profile.commander,
                                'avatar' : profile.avatar
                                }
            path = os.path.join(os.path.dirname(__file__), 'createprofile.html')
            self.response.out.write(template.render(path, template_values))


class StoreProfile(webapp.RequestHandler):
    """ receive validate and store the profile data, return to lobby """
    def post(self):
        # identify user
        user = users.get_current_user()
        # consider adding the profile to the sessionmanager in Lobby and picking it up here
        profile = UserProfile()
        profile.user=user
        profile.commander = self.request.get('commander')
        profile.rank = "commander"
        profile.avatar = eval(self.request.get('avatar'))
        # profile.last_login = time.time() (must be a datetime()
        profile.ships = "1000"
        profile.missions = "1110000000000"
        profile.spacecredits = 0.00
        profile.rendermode = 0
        profile.put()
        self.redirect('/')

def avatarspritepos(p):
    """ helper function: returns the (x,y) coordinates of avatar face nr "p" on the sprite image """
    return ((p % 8)*74+16,int(p / 8)*74)


class Epilogue(webapp.RequestHandler):

    def get(self):
        global sessionmanager
        user = users.get_current_user()
        spoils = sessionmanager.get_spoils(user)
        if (spoils):
            path = os.path.join(os.path.dirname(__file__), 'epilogue.html')
            self.response.out.write(template.render(path, spoils))
        else:
            self.redirect('/')
            

class Lobby(webapp.RequestHandler):

    def get(self):
        global sessionmanager
        """ Renders the lobby page """
        user = users.get_current_user()
        if user:
            profiles = UserProfile.gql("WHERE user = :1", user)
            profile=profiles.get()
            if profile:
                sessionmanager.enter_lobby(user)
                (avatarx, avatary) = avatarspritepos(profile.avatar)
                template_values = { 'commander': profile.commander,
                                    'avatarx' : avatarx,
                                    'avatary' : avatary,
                                    'last_here' : profile.last_login,
                                    'ships' : profile.ships,
                                    'players' : sessionmanager.list_lobby_users(),
                                    'missions' : availableMissions("1111111111"), # profile.missions),
                                    'rendermode' : 0
                                    }
                path = os.path.join(os.path.dirname(__file__), 'lobby.html')
                self.response.out.write(template.render(path, template_values))
            else:
                self.redirect('/createprofile')
        else:
            self.redirect(users.create_login_url(self.request.uri))


sessionmanager = SessionManager()
application = webapp.WSGIApplication([
    ('/', Lobby),
    ('/createprofile', CreateProfile),
    ('/storeprofile', StoreProfile),
    ('/entermission', EnterGame),
    ('/command', CommandReceiver),
    ('/epilogue', Epilogue)], debug=True)

def main():
    logging.getLogger().setLevel(logging.DEBUG)
    run_wsgi_app(application)


if __name__ == "__main__":
  main()    

